//main  300
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <ctype.h>
#include <sys/stat.h>
#include <fcntl.h>


#define COMMAND_SIZE 1024
#define CMDMPT_SIZE 100
#define FORMAT "[%s@%s %s]# "
#define ARGV_COUNT 128
#define ENV_CONUT 100

//命令行参数表
char* g_argv[ARGV_COUNT];
int g_argc = 0;

//环境变量表
char* g_env[ENV_CONUT];
int g_envc = 0;
//shell内部有全局的变量，存放调用进程的退出码
int exit_code = 0;

//存放用于重定向的内容
#define NO_REDIR 0      //没有重定向
#define INPUT_REDIR 1   //输入重定向
#define OUTPUT_REDIR 2  //输出重定向
#define APPEND_REDIR 3  //追加重定向
int redir_type = 0;
char* redir_file;


const char* GetUserName()
{
    const char* name = getenv("USER");
    return name == NULL?"None":name;
}
const char* GetHostName()
{
    const char* hostname = getenv("HOSTNAME");
    return hostname == NULL? "None":hostname;
}
const char* GetHome()
{
    return getenv("HOME");
}


////for test 
//const char* GetMYVAL()
//{
//    return getenv("MYVAL");
//}

//切割路径
const char* DirName(const char * pwd)
{
    #define SLASH "/"
    std::string s_pwd = pwd;
    if(s_pwd == SLASH)
    {
        return SLASH;
    }
    //处理后面有多个/的情况
    while(s_pwd[s_pwd.size()-1] == '/')
    {
        s_pwd.erase(s_pwd.size()-1);
    }


    auto it = s_pwd.rfind(SLASH);
    if(it == std::string::npos)
    {
        return "ERROR";
    }
    //std::string dir = s_pwd.substr(it + 1);
    //return dir.c_str();
    return s_pwd.substr(it+1).c_str();
}

const char* GetPWD()
{
    const char* pwd = getenv("PWD");
    return pwd == NULL?"None":DirName(pwd);
}

void MakeCommandPrompt(char* cmd_mpt,int size)
{
    snprintf(cmd_mpt,size,FORMAT,GetUserName(),GetHostName(),GetPWD());
}


void PrintCommandPrompt()
{
    char cmd_mpt[CMDMPT_SIZE];
    int size = sizeof(cmd_mpt);
    //制作CommandPrompt
    MakeCommandPrompt(cmd_mpt,size);
    //输出CommandPromat
    printf("%s",cmd_mpt);
    //把缓冲区内容刷新出去
    fflush(stdout);
}

bool GetCommandline(char* cmdline)
{
    char* c = fgets(cmdline,COMMAND_SIZE,stdin);
    cmdline[strlen(cmdline)-1] = 0;
    //读取错误或没有命令都返回错误
    if(strlen(cmdline) == 0)
        return false;
    return c == NULL?false:true;
}

void SkipSpace(const char* cmd,int& end)
{
    while(isspace(cmd[end]))
    {
        end++;//只要是空格就前移，确保end指向文件名字的第一个字符
    }

}

//ls -a -l >> log.txt
//ls -a -l > log.txt
void CheckRedir(char* cmd)
{
    redir_type = NO_REDIR;
    int start = 0;
    int end = strlen(cmd)-1;
    //>   >>   <
    while(start < end)
    {
        if(cmd[end] == '<')
        {
            //输入重定向
            redir_type =  INPUT_REDIR;
            cmd[end++] = 0;//为了确保后续分析命令时只得到前半部分
            //实现用于跳过空格的函数
            SkipSpace(cmd,end);
            redir_file = cmd + end;
            break;
        }
        else if(cmd[end] == '>')
        {
            if(cmd[end-1] == '>')
            {
                //追加重定向
                redir_type = APPEND_REDIR;
                cmd[end - 1] = 0;
            }
            else 
            {
                //输出重定向
                redir_type = OUTPUT_REDIR;
                cmd[end] = 0;
            }
            end++;
            SkipSpace(cmd,end);
            redir_file = cmd + end;
            break;
        }
        else 
        {
            end--;
        }
    }

}


//命令行分析 填充命令行参数表
bool CommandParse(char* cmd)
{
    #define SEP " "
    g_argc = 0;
    g_argv[g_argc++] = strtok(cmd,SEP);

    while((bool)(g_argv[g_argc++] = strtok(nullptr,SEP)));
    g_argc--;

    return true;
}

void PrintArgv()
{
    for(int i = 0; g_argv[i];i++)
    {
        printf("%d->%s\n",i,g_argv[i]);
    }
}
bool Cd()
{
    #define CWD_SIZE 100
    std::string curdir = getenv("PWD");
    char buffer[CWD_SIZE];
    memset(buffer,0,sizeof(buffer));
    if(g_argc == 1)
    {
       //跳转到家目录
        std::string home = GetHome();
        if(home.empty())return true;
        chdir(home.c_str());  
        curdir = home;
        //curdir = "/home/wxd";
        //printf("%s\n",curdir);
    }
    else 
    {
       //跳转到指定目录
        std::string where = g_argv[1];
        if(where == "~")
        {
            //跳转到家目录
             std::string home = GetHome();
             if(home.empty())return true;
             chdir(home.c_str());  

             curdir = home;
        }
        else if(where == "/")
        {
            chdir("/");
            curdir = "/";
        }
        else 
        {
            if(where.empty())
                return true;
            if(chdir(where.c_str()) == -1)
            {
                printf("%s does not exist!\n",where.c_str());
                return 1;
            }
            curdir = where;
        }
    }

    int flag = 0;
    //更新环境变量
    for(int i = 0;g_env[i];i++)
    {

        std::string env = g_env[i];
        auto pos = env.find("=");
        std::string env_name = env.substr(0,pos);
        if(env_name == "PWD")
        {
            g_env[i] = (char*)realloc(g_env[i],strlen(curdir.c_str())+5);
            if(g_env[i] == NULL)
            {
                perror("Cd::realloc fail:");
                exit(1);
            }
            snprintf(buffer,sizeof(buffer)-1,"PWD=%s",curdir.c_str());
            strcpy(g_env[i],buffer);
            flag = 1;
            break;
        }

    }
    if(flag == 0)
    {
        g_env[g_envc++] = (char*)malloc(strlen(curdir.c_str())+5);
        if(g_env[g_envc-1] == NULL)
        {
            perror("Cd::malloc fail:");
            exit(1);
        }
        snprintf(buffer,sizeof(buffer)-1,"PWD=%s",curdir.c_str());
        strcpy(g_env[g_envc-1],curdir.c_str());
        g_env[g_envc] = NULL;
    }

}

bool Echo()
{
    if(g_argc == 1)
    {
        return true;
    }
    else 
    {
        std::string str = g_argv[1];
        if(str == "$?")
        {
            std::cout<<exit_code<<std::endl;
            exit_code = 0;
        }
        else if(str[0] == '$')
        {
            std::string envname = str.substr(1);
            const char* env_value = getenv(envname.c_str());
            if(env_value == NULL)return 1;
            std::cout<< env_value<<std::endl;

        }
        else 
        {
            std::cout<<str.c_str()<<std::endl;
        }

    }

}
bool CheckAndExecBuiltin()
{
    std::string cmd = g_argv[0];
    if(cmd == "cd")
    {
        Cd();
        return true;
    }
    else if(cmd == "echo")
    {
        Echo();
        return true;
    }
    return false;
}

int Execute()
{
    pid_t id = fork();
    if(id == 0)
    {
        int fb = -1;
        //重定向
        if(redir_type == INPUT_REDIR)//<
        {
            fb = open(redir_file,O_RDONLY);
            if(fb < 0)exit(1);
            dup2(fb,0);
            close(fb);
        }
        else if(redir_type == OUTPUT_REDIR)//>
        {
            fb = open(redir_file,O_CREAT|O_WRONLY|O_TRUNC,0666);
            if(fb < 0)exit(1);
            dup2(fb,1);
            close(fb);
        }
        else if(redir_type == APPEND_REDIR)//>>
        {
            fb = open(redir_file,O_CREAT|O_WRONLY|O_APPEND,0666);
            if(fb < 0)exit(1);
            dup2(fb,1);
            close(fb);
        }
        //子进程
        execvp(g_argv[0],g_argv);    
        exit(1);
    }

    //父进程
    //shell父进程接收子进程的退出码
    int status = 0;
    waitpid(id,&status,0);
    exit_code = WIFEXITED(status);

    return 0;
}

//本身这个表是从系统的配置文件中读取的，我们的shell毕竟是个小程序，不支持脚本，所以我们选择从父进程的环境变量读取
bool InitEnv()
{
    //memset(g_env,0,sizeof(g_env));
    extern char** environ;
    //从父进程中读取，让我们自己来维护这张表
    g_envc = 0;
    for(;environ[g_envc];g_envc++)
    {
        g_env[g_envc] = (char*)malloc(strlen(environ[g_envc])+1);
        if(g_env[g_envc] == NULL)
        {
            perror("malloc fail:");
            exit(1);
        }
        strcpy(g_env[g_envc],environ[g_envc]);
    }
    g_env[g_envc++] = (char*)"MYVAL=6666666666666";
    g_env[g_envc] = NULL;

    //for(int i = 0;g_env[i];i++)
    //{
    //    putenv(g_env[i]);
    //}
    //让全局指针指向我们维护的表
    environ = g_env;
    return true;
}


int main()
{
    //shell启动时要创建环境变量表
    //本身这个表是从系统的配置文件中读取的，我们的shell毕竟是个小程序，不支持脚本，所以我们选择从父进程的环境变量读取
    InitEnv();
    //printf("%s\n",GetMYVAL());
    while(true)
    {
        //打印命令行提示符
        PrintCommandPrompt();
        //printf("[%s@%s %s]#",GetUserName(),GetHostName(),GetPWD());
         
        //读取命令
        char commandline[COMMAND_SIZE];
        if(!GetCommandline(commandline))
            continue;
        //判断重定向
        CheckRedir(commandline);
        
        ////测试一下
        //printf("redir_type:%d redir_file:%s\n",redir_type,redir_file);
        //处理命令字符串，命令行分析,填充命令行参数表
        CommandParse(commandline);

        //判断并执行内建命令
        if(CheckAndExecBuiltin())
            continue;
        

        //执行命令-普通命令
        Execute();
        
        
    }
    //char* c = fgets(commandline,sizeof(commandline),stdin);
    //if(c == NULL) return 1;
    //commandline[strlen(commandline)-1] = '\0';
    //printf("echo %s\n",commandline);
    return 0;
}
